//References
//paddle & ball clipart: http://www.chumpysclipart.com/illustration/4292/picture_of_a_pingpong_paddle_and_ball
//arrow clipart: http://www.fedbybirds.com/2008/07/
//

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Devices.Sensors;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;

namespace BouncinBall
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class BouncingBallGame : Microsoft.Xna.Framework.Game
    {
        #region Constants

        //GAME CONSTANTS
        public const float STARTING_HEIGHT = 5F;
        public const float BALL_VISIBLE_HEIGHT = 1F;
        public const float GAMEOVER_HEIGHT = -1F;
        public const float GAME_GRAVITY = -9.8F;//-9.82F;

        //DEBUG CONSTANTS
        public const GameMode GAMETYPE = GameMode.DebugVerticalBounceFixed; //should come from main menu selection
        public const float INIT_X = 0.0F;
        public const float INIT_Y = 0.0F;
        public const float INIT_Z = STARTING_HEIGHT;
        //public const float INIT_X_NEARNORTH = 0F;
        //public const float INIT_Y_NEARNORTH = Indicators.LocatorManager.NEARMID_THRESHOLD / 2;
        //public const float INIT_Z_NEARNORTH = STARTING_HEIGHT;
        //public const float INIT_X_MIDDLEEAST = (Indicators.LocatorManager.NEARMID_THRESHOLD + Indicators.LocatorManager.MIDFAR_THRESHOLD) / 2;
        //public const float INIT_Y_MIDDLEEAST = 0F;
        //public const float INIT_Z_MIDDLEEAST = STARTING_HEIGHT;
        //public const float INIT_X_FARSOUTH = 0F;
        //public const float INIT_Y_FARSOUTH = -1 * Indicators.LocatorManager.MIDFAR_THRESHOLD - 1;
        //public const float INIT_Z_FARSOUTH = STARTING_HEIGHT;
        //public const float INIT_X_FARWESTNEARNORTH = -1 * Indicators.LocatorManager.MIDFAR_THRESHOLD - 1;
        //public const float INIT_Y_FARWESTNEARNORTH = Indicators.LocatorManager.NEARMID_THRESHOLD / 2;
        //public const float INIT_Z_FARWESTNEARNORTH = STARTING_HEIGHT;
        public const float LATERAL_BOUNCE_VELOCITY = 0.06F;

        //Depth constants for images -- currently not used, use SpriteSortMode if want to use
        public const float PADDLE_DEPTH = 0.0F;
        public const float SHADOW_DEPTH = 0.1F;
        public const float BALL_DEPTH = 0.2F;
        public const float PADDLE_DEPTH_OVER = 0.3F;
        public const float ARROW_DEPTH = 0.7F;
        public const float TEXT_DEPTH = 1.0F;

        #endregion Constants

        #region Enumerations

        public enum GameMode
        {
            DebugDrop, //with touch reposition
            //DebugDropNearNorth, //indicator tests
            //DebugDropMiddleEast,
            //DebugDropFarSouth,
            //DebugDropFarWestNearNorth,
            DebugShadowTest, //test shadow w/o ball
            DebugVerticalBounceFixed, //ball moves laterally with paddle (just bounces up and down)
            DebugVerticalBounceFree, //able to move paddle in space (but ball bounces up regardless of paddle orientation, if hit)
            DebugLateralBounce,
            PreGame, //touch to play
            Normal,
            PostGame //touch to restart
        }

        public enum ArrowOrient
        {
            left,
            right,
            up,
            down
        }

        #endregion Enumerations

        #region Properties

        public GameMode Mode { get { return _mode; } }
        private GameMode _mode;
        public bool IsPlaying
        {
            get
            {
                return !(Mode == GameMode.PreGame || Mode == GameMode.PostGame);
            }
        }
        public Components.FieldManager Field { get { return _field; } }
        Components.FieldManager _field;
        public int PixelsPerMeter { get { return _pixRatio; } }
        private int _pixRatio;
        public int ScreenHeightPixels { get { return _graphics.PreferredBackBufferHeight; } }
        public int ScreenWidthPixels { get { return _graphics.PreferredBackBufferWidth; } }
        GraphicsDeviceManager _graphics;
        public SpriteBatch SpriteBatch { get { return _spriteBatch; } }
        private SpriteBatch _spriteBatch;
        private int _hits;
        private int _score;
        private SpriteFont _fontFooter;
        private SpriteFont _fontError;
        //Motion API //http://msdn.microsoft.com/en-us/library/hh202984%28v=vs.92%29.aspx
        public bool MotionActive { get { return string.IsNullOrWhiteSpace(_motionErrorMsg); } }
        private string _motionErrorMsg;
        public MotionReading MotionSensorReading
        {
            get
            {
                if (_motion == null)
                    return new MotionReading(); //return default value if not being used
                else
                    return _motion.CurrentValue;
            }
        }
        private Motion _motion;
        //sound
        private SoundEffect _soundEnd;

        //DEBUG
        //private Texture2D _debugBall;
        //private Texture2D _debugShadow;

        private string DisplayText
        {
            get
            {
                if (Mode == GameMode.PreGame)
                    return Strings.PreGame_Footer;
                else if (IsPlaying)
                    return Strings.Footer_HitsPrefix + _hits.ToString("000");
                else if (Mode == GameMode.PostGame)
                    return Strings.PostGame_Footer;
                else
                    return string.Empty;
            }
        }

        #endregion Properties

        #region Constructors

        public BouncingBallGame()
        {
            _graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            _graphics.SupportedOrientations = DisplayOrientation.Portrait;
            _graphics.PreferredBackBufferHeight = 800;
            _graphics.PreferredBackBufferWidth = 480;

            // Frame rate is 30 fps by default for Windows Phone.
            TargetElapsedTime = TimeSpan.FromTicks(333333);
            IsFixedTimeStep = false;

            // Extend battery life under lock.
            InactiveSleepTime = TimeSpan.FromSeconds(1);
        }

        #endregion Constructors

        #region Overrides

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            //Initialize screen space to field space ratio
            _pixRatio = (int)(_graphics.PreferredBackBufferWidth / BouncinBall.Components.FieldManager.PADDLE_DIAMETER); //use paddle width if move to rectangular paddle

            _mode = GameMode.PreGame;

            // TODO: Add your initialization logic here
            Components.Paddle paddle = new Components.Paddle(this, BouncinBall.Components.FieldManager.PADDLE_DIAMETER); //Initialize paddle
            Components.Ball ball = new Components.Ball(this, BouncinBall.Components.FieldManager.BALL_DIAMETER); //Initialize ball
            Components.Shadow shadow = new Components.Shadow(this); //Initialize shadow

            Indicators.LocatorArrow leftArrow = new Indicators.LocatorArrow(this, ArrowOrient.left);
            Indicators.LocatorArrow rightArrow = new Indicators.LocatorArrow(this, ArrowOrient.right);
            Indicators.LocatorArrow upArrow = new Indicators.LocatorArrow(this, ArrowOrient.up);
            Indicators.LocatorArrow downArrow = new Indicators.LocatorArrow(this, ArrowOrient.down);

            //Initialize playing field, place paddle, ball, shadow in field
            _field = new Components.FieldManager(this, GAME_GRAVITY);
            _field.SetComponents(ball, paddle, shadow, leftArrow, rightArrow, upArrow, downArrow);
            _field.BallHit += new BouncinBall.Components.FieldManager.BallHitEventHandler(FieldManager_BallHit);
            //Initialize locator manager, give reference to field, subscribe to ball position change event

            Components.Add(paddle);
            Components.Add(shadow);
            Components.Add(ball);
            Components.Add(_field);
            Components.Add(leftArrow);
            Components.Add(rightArrow);
            Components.Add(upArrow);
            Components.Add(downArrow);

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            _spriteBatch = new SpriteBatch(GraphicsDevice); //sprites loaded by drawable components
            _fontFooter = Content.Load<SpriteFont>("footer");
            _fontError = Content.Load<SpriteFont>("error");

            _soundEnd = Content.Load<SoundEffect>("shatter");

            if (false)//Motion.IsSupported)
            {
                // If the Motion object is null, initialize it and add a CurrentValueChanged
                // event handler.
                if (_motion == null)
                {
                    _motion = new Motion();
                    //_motion.TimeBetweenUpdates = TimeSpan.FromMilliseconds(20);
                    //_motion.CurrentValueChanged += new EventHandler<SensorReadingEventArgs<MotionReading>>(Paddle_CurrentValueChanged);
                }

                // Try to start the Motion API.
                try
                {
                    _motion.Start();
                }
                catch (Exception ex)
                {
                    _motionErrorMsg = "Motion Not\nStarted!";
                }
            }
            else
                _motionErrorMsg = "Motion\nUnsupported!";
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            if ((!IsPlaying && (Mode == GameMode.PreGame || Mode == GameMode.PostGame)) || Mode == GameMode.DebugVerticalBounceFixed)
            {
                // Process touch events
                TouchCollection touchCollection = TouchPanel.GetState();
                foreach (TouchLocation tl in touchCollection)
                {
                    if (tl.State == TouchLocationState.Pressed)
                    {
                        if (Mode == GameMode.PreGame || Mode == GameMode.DebugVerticalBounceFixed) //tap a second time to start new game
                        {
                            if (Mode == GameMode.PreGame)
                                _hits = _score = 0;
                            _mode = GAMETYPE;
                            if (Mode == GameMode.DebugDrop || Mode == GameMode.Normal || Mode == GameMode.DebugVerticalBounceFixed) //change position on tap
                                _field.Ball.Position = Utility.Helper.FieldPositionFromScreenPosition(tl.Position, 480, 632, _pixRatio, STARTING_HEIGHT);
                                //_field.Ball.Position = Utility.Helper.FieldPositionFromScreenPosition(tl.Position, _graphics.PreferredBackBufferWidth,
                                //    _graphics.PreferredBackBufferHeight, _pixRatio, STARTING_HEIGHT);
                            _field.SetArrowVisibility(true);
                            _field.StartGame(GAME_GRAVITY);
                        }
                        else if (Mode == GameMode.PostGame) //tap once to advance to pre-game mode
                        {
                            _field.SetArrowVisibility(false);
                            _mode = GameMode.PreGame;
                        }
                        else 

                        break;
                    }
                }
            }

            // TODO: Add your update logic here
            base.Update(gameTime); //Base update applies component updates

            if (IsPlaying)
            {
                //Determine if lost game
                if (_field.Ball.Position.Height < GAMEOVER_HEIGHT)
                {
                    _soundEnd.Play();
                    _field.StopGame();
                    _mode = GameMode.PostGame;
                }
                
                //Score updated in event handler
            }
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            
            // TODO: Add your drawing code here
            _spriteBatch.Begin(SpriteSortMode.FrontToBack, BlendState.AlphaBlend);

            //DEBUG CODE
            //_field.Ball.Visible = false;
            //_field.Paddle.Visible = false;
            //_spriteBatch.Draw(_debugBall, new Vector2(200, 200), null, Color.White, 0, Vector2.Zero, 1, SpriteEffects.None, 0.2F);
            //_spriteBatch.Draw(_debugBall, new Vector2(216, 216), null, Color.White, 0, Vector2.Zero, 1, SpriteEffects.None, 0.1F);
            //_spriteBatch.Draw(_debugBall, new Vector2(240, 240), null, Color.White, 0, Vector2.Zero, 1, SpriteEffects.None, 1);
            //_spriteBatch.Draw(_debugShadow, new Vector2(0, 0), null, Color.White, 0, Vector2.Zero, 1, SpriteEffects.None, 0.5F);

            base.Draw(gameTime);

            if(!string.IsNullOrWhiteSpace(_motionErrorMsg))
                _spriteBatch.DrawString(_fontError, _motionErrorMsg, new Vector2((_graphics.PreferredBackBufferWidth - _fontError.MeasureString(_motionErrorMsg).X) / 2, 300), Color.White, 0F, new Vector2(), 1F, SpriteEffects.None, TEXT_DEPTH);
            _spriteBatch.DrawString(_fontFooter, DisplayText, new Vector2((_graphics.PreferredBackBufferWidth - _fontFooter.MeasureString(DisplayText).X) / 2, 672), Color.Orange, 0F, new Vector2(), 1F, SpriteEffects.None, TEXT_DEPTH);

            _spriteBatch.End();
        }

        #endregion Overrides

        #region Event Handlers

        void FieldManager_BallHit(Components.FieldManager.BallHitEventArgs evt)
        {
            _hits++;
            _score++;
        }

        //void Paddle_CurrentValueChanged(object sender, SensorReadingEventArgs<MotionReading> reading)
        //{
        //    // This event arrives on a background thread. Use BeginInvoke to call
        //    // CurrentValueChanged on the UI thread.
        //    //Dispatcher.BeginInvoke(() => MotionCurrentValueChanged(reading.SensorReading));
        //    if (_motion.IsDataValid)
        //        _field.UpdatePaddle(reading.SensorReading);
        //}

        #endregion Event Handlers
    }
}
